/* eslint-disable @typescript-eslint/indent */
import { useCondition } from "../../../condition/src/composition/use-condition";
import { EditorType } from "../../../dynamic-form";
import { ConditionValue } from "../../../condition/src/composition/condition-value/types";
import { CheckBoxValue } from "../../../condition/src/composition/condition-value/checkbox-value";
import { ComboListValue } from "../../../condition/src/composition/condition-value/dropdown-value";
import { ComboLookupValue } from "../../../condition/src/composition/condition-value/combo-lookup-value";
import { InputGroupValue } from "../../../condition/src/composition/condition-value/input-group-value";
import { DatePickerValue } from "../../../condition/src/composition/condition-value/date-picker-value";
import { DateTimePickerValue } from "../../../condition/src/composition/condition-value/datetime-picker-value";
import { DateRangeValue } from "../../../condition/src/composition/condition-value/date-range-value";
import { LookupValue } from "../../../condition/src/composition/condition-value/lookup-value";
import { MonthPickerValue } from "../../../condition/src/composition/condition-value/month-picker-value";
import { MonthRangeValue } from "../../../condition/src/composition/condition-value/month-range-value";
import { NumberRangeValue } from "../../../condition/src/composition/condition-value/number-range-value";
import { NumberSpinnerValue } from "../../../condition/src/composition/condition-value/number-spinner-value";
import { RadioGroupValue } from "../../../condition/src/composition/condition-value/radio-group-value";
import { TextValue } from "../../../condition/src/composition/condition-value/text-value";
import { Condition, ConditionGroup, ValueType } from "../../../condition/src/types";
import { ref } from "vue";
import { DataFilter, DataGridHeaderCell, FilterFunction, FilterRelation, UseFilter } from "./types";
import { CompareType } from "../../../condition/src/composition/use-compare";
import { DataGridColumn } from "../data-grid.props";

type CompareFunction = (dataItem: Record<string, any>, field: string, value: any) => boolean;

const compareOperators = ['equal', 'notEqual', 'greaterThan', 'greaterThanOrEqual', 'lessThan', 'lessThanOrEqual',
    'contain', 'startWidth', 'endWidth'];

const textCompare: Record<string, CompareFunction> = {
    'equal': (dataItem: Record<string, any>, field: string, textValue: string) => {
        return dataItem && dataItem[field] === textValue;
    },
    'notEqual': (dataItem: Record<string, any>, field: string, textValue: string) => {
        return dataItem && dataItem[field] !== textValue;
    },
    'contain': (dataItem: Record<string, any>, field: string, textValue: string) => {
        if (dataItem) {
            const sourceString = dataItem[field] as string || '';
            return sourceString.indexOf(textValue) > -1;
        }
        return false;
    },
    'startWidth': (dataItem: Record<string, any>, field: string, textValue: string) => {
        if (dataItem) {
            const sourceString = dataItem[field] as string || '';
            return sourceString.startsWith(textValue);
        }
        return false;
    },
    'endWidth': (dataItem: Record<string, any>, field: string, textValue: string) => {
        if (dataItem) {
            const sourceString = dataItem[field] as string || '';
            return sourceString.endsWith(textValue);
        }
        return false;
    }
};

const booleanCompare: Record<string, CompareFunction> = {
    'equal': (dataItem: Record<string, any>, field: string, booleanValue: boolean) => {
        return dataItem && dataItem[field] === booleanValue;
    },
    'notEqual': (dataItem: Record<string, any>, field: string, booleanValue: boolean) => {
        return dataItem && dataItem[field] !== booleanValue;
    }
};

const dateTimeCompare: Record<string, CompareFunction> = {
    'equal': (dataItem: Record<string, any>, field: string, dateTimeValue: string | number | Date) => {
        if (dataItem) {
            const targetDateTimeValue = new Date(new Date(dateTimeValue).toLocaleDateString()).valueOf();
            return new Date(new Date(dataItem[field]).toLocaleDateString()).valueOf() === targetDateTimeValue;
        }
        return false;
    },
    'notEqual': (dataItem: Record<string, any>, field: string, dateTimeValue: string | number | Date) => {
        if (dataItem) {
            const targetDateTimeValue = new Date(new Date(dateTimeValue).toLocaleDateString()).valueOf();
            return new Date(new Date(dataItem[field]).toLocaleDateString()).valueOf() !== targetDateTimeValue;
        }
        return false;
    },
    'greaterThan': (dataItem: Record<string, any>, field: string, dateTimeValue: string | number | Date) => {
        if (dataItem) {
            const targetDateTimeValue = new Date(new Date(dateTimeValue).toLocaleDateString()).valueOf();
            return new Date(new Date(dataItem[field]).toLocaleDateString()).valueOf() > targetDateTimeValue;
        }
        return false;
    },
    'greaterThanOrEqual': (dataItem: Record<string, any>, field: string, dateTimeValue: string | number | Date) => {
        if (dataItem) {
            const targetDateTimeValue = new Date(new Date(dateTimeValue).toLocaleDateString()).valueOf();
            return new Date(new Date(dataItem[field]).toLocaleDateString()).valueOf() >= targetDateTimeValue;
        }
        return false;
    },
    'lessThan': (dataItem: Record<string, any>, field: string, dateTimeValue: string | number | Date) => {
        if (dataItem) {
            const targetDateTimeValue = new Date(new Date(dateTimeValue).toLocaleDateString()).valueOf();
            return new Date(new Date(dataItem[field]).toLocaleDateString()).valueOf() < targetDateTimeValue;
        }
        return false;
    },
    'lessThanOrEqual': (dataItem: Record<string, any>, field: string, dateTimeValue: string | number | Date) => {
        if (dataItem) {
            const targetDateTimeValue = new Date(new Date(dateTimeValue).toLocaleDateString()).valueOf();
            return new Date(new Date(dataItem[field]).toLocaleDateString()).valueOf() <= targetDateTimeValue;
        }
        return false;
    }
};

const enumCompare: Record<string, CompareFunction> = {
    'equal': (dataItem: Record<string, any>, field: string, enumValues: string) => {
        if (dataItem) {
            const enumArray = String(enumValues).split(',');
            return enumArray.includes(String(dataItem[field]));
        }
        return false;
    },
    'notEqual': (dataItem: Record<string, any>, field: string, enumValues: string) => {
        if (dataItem) {
            const enumArray = String(enumValues).split(',');
            return enumArray.findIndex((enumValue: string) => enumValue === String(dataItem[field])) === -1;
        }
        return false;
    },
};

const numericCompare: Record<string, CompareFunction> = {
    'equal': (dataItem: Record<string, any>, field: string, numericValue: number) => {
        if (dataItem) {
            const filterValue = Number.parseFloat(String(numericValue));
            const matchingValue = Number.parseFloat(String(dataItem[field]));
            return isNaN(filterValue) ? isNaN(matchingValue) : filterValue === matchingValue;
        }
        return false;
    },
    'notEqual': (dataItem: Record<string, any>, field: string, numericValue: number) => {
        if (dataItem) {
            const filterValue = Number.parseFloat(String(numericValue));
            const matchingValue = Number.parseFloat(String(dataItem[field]));
            return isNaN(filterValue) ? true : filterValue !== matchingValue;
        }
        return false;
    },
    'greaterThan': (dataItem: Record<string, any>, field: string, numericValue: number) => {
        if (dataItem) {
            const filterValue = Number.parseFloat(String(numericValue));
            const matchingValue = Number.parseFloat(String(dataItem[field]));
            return isNaN(filterValue) ? isNaN(matchingValue) : matchingValue > filterValue;
        }
        return false;
    },
    'greaterThanOrEqual': (dataItem: Record<string, any>, field: string, numericValue: number) => {
        if (dataItem) {
            const filterValue = Number.parseFloat(String(numericValue));
            const matchingValue = Number.parseFloat(String(dataItem[field]));
            return isNaN(filterValue) ? isNaN(matchingValue) : matchingValue >= filterValue;
        }
        return false;
    },
    'lessThan': (dataItem: Record<string, any>, field: string, numericValue: number) => {
        if (dataItem) {
            const filterValue = Number.parseFloat(String(numericValue));
            const matchingValue = Number.parseFloat(String(dataItem[field]));
            return isNaN(filterValue) ? isNaN(matchingValue) : matchingValue < filterValue;
        }
        return false;
    },
    'lessThanOrEqual': (dataItem: Record<string, any>, field: string, numericValue: number) => {
        if (dataItem) {
            const filterValue = Number.parseFloat(String(numericValue));
            const matchingValue = Number.parseFloat(String(dataItem[field]));
            return isNaN(filterValue) ? isNaN(matchingValue) : matchingValue <= filterValue;
        }
        return false;
    }
};

export function useFilter(): UseFilter {

    const conditions = ref<Condition[]>([]);
    const columnFilterConditionMap = new Map<string, Condition>();
    const { loadConditionGroup } = useCondition();

    const typeToConditionEditorName = new Map<string, string>([
        ['boolean', 'check-box'],
        ['date', 'date-picker'],
        ['datetime', 'datetime-picker'],
        ['number', 'number-spinner'],
        ['string', 'text'],
        ['text', 'text'],
        ['enum', 'combo-list'],
        ['reference', 'lookup']
    ]);

    const editorTypeToCompares = new Map<EditorType, Record<string, CompareFunction>>([
        ['check-box', booleanCompare],
        ['combo-list', enumCompare],
        ['combo-lookup', textCompare],
        ['date-picker', dateTimeCompare],
        ['date-range', dateTimeCompare],
        ['datetime-picker', dateTimeCompare],
        ['datetime-range', dateTimeCompare],
        ['month-picker', dateTimeCompare],
        ['month-range', dateTimeCompare],
        ['year-picker', dateTimeCompare],
        ['year-range', dateTimeCompare],
        ['input-group', textCompare],
        ['lookup', textCompare],
        ['number-range', numericCompare],
        ['number-spinner', numericCompare],
        ['radio-group', enumCompare],
        ['text', textCompare]
    ]);

    function createConditionValue(editorType: EditorType, fieldValue: any): ConditionValue {
        switch (editorType) {
            case 'check-box':
                return new CheckBoxValue({ value: fieldValue } as any);
            case 'combo-list':
                return new ComboListValue({ value: fieldValue } as any);
            case 'combo-lookup':
                return new ComboLookupValue({ value: fieldValue } as any);
            case 'input-group':
                return new TextValue({ value: fieldValue });
            case 'date-picker':
                return new DatePickerValue({ value: fieldValue } as any);
            case 'date-range':
                return new DateRangeValue({ value: fieldValue } as any);
            case 'datetime-picker':
                return new DateTimePickerValue({ value: fieldValue } as any);
            case 'datetime-range':
                return new DateRangeValue({ value: fieldValue } as any);
            case 'lookup':
                return new LookupValue({ value: fieldValue } as any);
            case 'month-picker':
                return new MonthPickerValue({ value: fieldValue } as any);
            case 'month-range':
                return new MonthRangeValue({ value: fieldValue } as any);
            case 'number-range':
                return new NumberRangeValue({ value: fieldValue } as any);
            case 'number-spinner':
                return new NumberSpinnerValue({ value: fieldValue } as any);
            case 'radio-group':
                return new RadioGroupValue({ value: fieldValue } as any);
            default:
                return new TextValue({ value: fieldValue } as any);
        }
    }

    function getFilterEditorType(column: DataGridColumn) {
        const fieldType = column?.dataType || 'string';
        return (column?.editor?.type || typeToConditionEditorName.get(fieldType) || 'text') as EditorType;
    }

    function addColumnFilter(headerCell: DataGridHeaderCell) {
        const id = `field_filter_${headerCell.field}`;
        const conditionId = Date.now();
        const fieldCode = headerCell.field;
        const fieldName = headerCell.column?.title || '';
        const fieldType = headerCell.column?.dataType || 'string';
        const compareType = CompareType.Equal;
        const editorType = (headerCell.column?.editor?.type || typeToConditionEditorName.get(fieldType) || 'text') as EditorType;
        const value = createConditionValue(editorType, headerCell.filterValue as any);
        const valueType = (editorType === 'lookup' || editorType === 'combo-lookup') ?
            ValueType.SmartHelp : (editorType === 'combo-list' ? ValueType.Enum : ValueType.Value);
        columnFilterConditionMap.set(id, { id, fieldCode, fieldName, compareType, valueType, value, conditionId });
        conditions.value = Array.from(columnFilterConditionMap.values());
    }

    function removeColumnFilter(headerCell: DataGridHeaderCell) {
        const id = `field_filter_${headerCell.field}`;
        columnFilterConditionMap.delete(id);
        conditions.value = Array.from(columnFilterConditionMap.values());
    }

    function removeCondition(id: string) {
        columnFilterConditionMap.delete(id);
        conditions.value = Array.from(columnFilterConditionMap.values());
    }

    function clearCondition() {
        columnFilterConditionMap.clear();
        conditions.value = [];
    }

    function getCompareFunction(condition: Condition): CompareFunction {
        const { editorType } = condition.value;
        const compareType = condition.compareType || '0';
        const compareName = compareOperators[Number(compareType)];
        const compares = editorTypeToCompares.get(editorType) as Record<string, CompareFunction>;
        const compareFunction = compares[compareName];
        return compareFunction;
    }

    function converterConditionGroupToDataFilter(conditionGroup: ConditionGroup): DataFilter {
        const relation = (conditionGroup.relation === 2 ? 0 : 1) as FilterRelation;
        const filterFunctions: FilterFunction[] = conditionGroup.items.map((condition: Condition) => {
            const compareFunction = getCompareFunction(condition);
            const filterFunction = (dataItem: any) => {
                return compareFunction(dataItem, condition.fieldCode, condition.value.getValue());
            };
            return filterFunction;
        });
        const children = (conditionGroup.children && conditionGroup.children.length) ?
            conditionGroup.children.map((conditionGroup: ConditionGroup) => converterConditionGroupToDataFilter(conditionGroup)) : [];
        return { relation, filterFunctions, children };
    }

    function createDataFilter(): DataFilter {
        const conditionGroup = loadConditionGroup(conditions.value);
        return converterConditionGroupToDataFilter(conditionGroup);
    }

    function applyFilter(dataItem: any, dataFilter: DataFilter) {
        const filterResult: boolean[] = dataFilter.filterFunctions.map((filterFunction: FilterFunction) => filterFunction(dataItem));
        const filterGroupResult: boolean[] = dataFilter.children.map((subFilter: DataFilter) => applyFilter(dataItem, subFilter));
        if (dataFilter.relation === 1) {
            return filterResult.every((result: boolean) => result) && filterGroupResult.every((result: boolean) => result);
        }
        return filterResult.includes(true) || filterGroupResult.includes(true);
    }

    function apply(dataItem: any) {
        const dataFilter = createDataFilter();
        return applyFilter(dataItem, dataFilter);
    }

    return { addColumnFilter, apply, clearCondition, conditions, getFilterEditorType, removeColumnFilter, removeCondition };
}
